package edu.mapi.ir.gui;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.geom.Point2D;
import java.io.File;
import java.util.Vector;
import java.util.prefs.Preferences;

import javax.swing.AbstractAction;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JOptionPane;
import javax.swing.UIManager;

import edu.mapi.ir.MazeWalker;
import edu.mapi.ir.StateListener;
import edu.mapi.ir.controllers.AStarController;
import edu.mapi.ir.controllers.Actuation;
import edu.mapi.ir.planning.AStarSearch;
import edu.mapi.ir.planning.SimpleMap;
import edu.mapi.ir.state.EstimatedState;
import edu.mapi.ir.viewer.GridPosition;
import edu.mapi.ir.viewer.Maze;
import edu.mapi.ir.viewer.MazePanel;
import edu.mapi.ir.viewer.MousePlayer;

/**
 * @author ZP
 */
public class MazeWalkerApp extends JFrame implements StateListener {

	private Maze maze = null;
	private Vector<GridPosition> grid = null;	
	private Preferences prefs = Preferences.systemNodeForPackage(MazeWalkerApp.class);
	private String host = prefs.get("host", "127.0.0.1");
	private MazePanel mazePanel = new MazePanel();
	private MousePlayer estimatedPos = null;
	private SimpleMap simpleMap = null;
	
	public MazeWalkerApp() {
		createMenus();
		setTitle("MazeWalker");		
		setLayout(new BorderLayout(2,2));
		add(mazePanel, BorderLayout.CENTER);
		
		String mazeFilename = prefs.get("mapfile", "c:/maze1.xml");
		try {
			maze = Maze.readFile(new File(mazeFilename));
			mazePanel.setMaze(maze);
			simpleMap = new SimpleMap(maze);
			mazePanel.addLayer(simpleMap);
		}
		catch (Exception e) {
			maze = new Maze();
			mazePanel.setMaze(maze);
			simpleMap = new SimpleMap(maze);
			mazePanel.addLayer(simpleMap);
			mazePanel.updateLayers();
			mazePanel.repaint();
			return;
		}
		
		String gridStr = prefs.get("gridfile", "c:/grid1.xml");
		try {
			grid = GridPosition.readFile(new File(gridStr));
			if (maze != null) {
				maze.setGrid(grid);
				simpleMap.setCurrentPath(new AStarSearch(maze).getBestPath(grid.firstElement().position, maze.getBeacon().getPosition(), Math.toRadians(grid.firstElement().direction)));						
			}
			estimatedPos = new MousePlayer(grid.firstElement());
			mazePanel.clearMice();
			mazePanel.addMouse(estimatedPos);
			
			mazePanel.updateLayers();
			mazePanel.repaint();	
		}
		catch (Exception e) {
			e.printStackTrace();
		}
		
		
	}
	
	private void createMenus() {
		JMenuBar menubar = new JMenuBar();
		
		JMenu file = new JMenu("File");
		
		AbstractAction connect = new AbstractAction("Connect...", GuiUtils.getIcon("imgs/start.png")) {
			public void actionPerformed(ActionEvent e) {
				String input = JOptionPane.showInputDialog(null, "Please provide the server hostname", "127.0.0.1");				
				if (input != null) {
					host = input;
					prefs.put("hostname", input);
					try {
						MazeWalker client = new MazeWalker();
						if (maze != null && grid != null) {
							EstimatedState state = new EstimatedState();
							
							state.setX(grid.firstElement().position.getX());
							state.setY(grid.firstElement().position.getY());
							state.setRotation(Math.toRadians(grid.firstElement().direction));
							
							client.setState(state);														
							client.setActiveController(new AStarController(maze));
							//client.setActiveController(new StopController());
							client.addStateListener(MazeWalkerApp.this);
						}
						
						client.start(host);			
					}
					catch (Exception ex) {
						GuiUtils.errorMessage(MazeWalkerApp.this, "Error connecting to server", ex.getMessage());
					}
				}
			}
		};		
		file.add(connect);
		
		file.addSeparator();
		
		AbstractAction localMode = new AbstractAction("Run Locally", GuiUtils.getIcon("imgs/start.png")) {
			public void actionPerformed(ActionEvent e) {
				final EstimatedState state = new EstimatedState();
				
				state.setX(grid.firstElement().position.getX());
				state.setY(grid.firstElement().position.getY());
				state.setRotation(Math.toRadians(grid.firstElement().direction));
				
				final AStarController myController = new AStarController(maze);
				
				Thread t = new Thread() {
					@Override
					public void run() {
						Actuation lastActuation = new Actuation(0,0);

						while (!myController.isComplete()) {
							Actuation act = myController.decide(lastActuation, state);							
							state.actuate(act.leftPower, act.rightPower);
							MazeWalkerApp.this.stateChanged(state);
							lastActuation = act;
							try {
								Thread.sleep(30);
							}
							catch (Exception e) {
								e.printStackTrace();
							}
						}
					}
				};
				t.start();
				
			}
		};
		
		file.add(localMode);
		
		AbstractAction openMaze = new AbstractAction("Select Maze...", GuiUtils.getIcon("imgs/maze.png")) {
			public void actionPerformed(ActionEvent e) {
				String mazeStr = prefs.get("mapfile", "c:/maze1.xml");
				
				JFileChooser jfc = new JFileChooser(new File(mazeStr));
				jfc.setAcceptAllFileFilterUsed(true);
				jfc.setDialogTitle("Select maze");		
				int res = jfc.showOpenDialog(null);
				if (res == JFileChooser.CANCEL_OPTION)
					return;
				
				File maze_file = jfc.getSelectedFile();
				try {
					
					maze = Maze.readFile(maze_file);
										
					mazePanel.setMaze(maze);
					simpleMap = new SimpleMap(maze);
					mazePanel.addLayer(simpleMap);
					
					if (grid != null) {
						maze.setGrid(grid);
						mazePanel.clearMice();
						
						simpleMap.setCurrentPath(new AStarSearch(maze).getBestPath(grid.firstElement().position, maze.getBeacon().getPosition(), Math.toRadians(grid.firstElement().direction)));
						estimatedPos = new MousePlayer(grid.firstElement());
						
						mazePanel.addMouse(estimatedPos);
						
						mazePanel.updateLayers();
						mazePanel.repaint();	
					}
					
					prefs.put("mapfile", maze_file.getAbsolutePath());
				}
				catch (Exception ex) {					
					GuiUtils.errorMessage(MazeWalkerApp.this, "Error reading grid file", ex.getClass().getSimpleName()+" while opening maze");
					ex.printStackTrace();
				}
			}
		};
		file.add(openMaze);
		
		AbstractAction openGrid = new AbstractAction("Select Grid...", GuiUtils.getIcon("imgs/grid.png")) {
			public void actionPerformed(ActionEvent e) {
				String gridStr = prefs.get("gridfile", "c:/grid1.xml");
				
				JFileChooser jfc = new JFileChooser(new File(gridStr));
				jfc.setAcceptAllFileFilterUsed(true);
				jfc.setDialogTitle("Select grid");		
				int res = jfc.showOpenDialog(null);
				if (res == JFileChooser.CANCEL_OPTION)
					return;	
				
				File grid_file = jfc.getSelectedFile();
								
				try {
					grid = GridPosition.readFile(grid_file);
					if (maze != null) {
						maze.setGrid(grid);
						simpleMap.setCurrentPath(new AStarSearch(maze).getBestPath(grid.firstElement().position, maze.getBeacon().getPosition(), Math.toRadians(grid.firstElement().direction)));						
					}
					estimatedPos = new MousePlayer(grid.firstElement());
					mazePanel.clearMice();
					mazePanel.addMouse(estimatedPos);					
					mazePanel.updateLayers();
					mazePanel.repaint();	
					prefs.put("gridfile", grid_file.getAbsolutePath());
				}
				catch (Exception ex) {					
					GuiUtils.errorMessage(MazeWalkerApp.this, "Error reading maze file", ex.getClass().getSimpleName()+" while opening maze");
					ex.printStackTrace();
				}
			}
		};
		file.add(openGrid);
		
		file.addSeparator();
		
		file.add(new AbstractAction("Exit", GuiUtils.getIcon("imgs/exit.png")) {
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});		
		
		menubar.add(file);
		setJMenuBar(menubar);
	}
	
	public void stateChanged(EstimatedState state) {
		if (estimatedPos != null) {
			estimatedPos.setPosition(new Point2D.Double(state.getX(), state.getY()));
			estimatedPos.setDirection(Math.toDegrees(state.getRotation()));
			for (int i = 0; i < 4; i++) {
				//System.out.print("Sensor("+i+")="+state.distanceSensors[i]);
				estimatedPos.distanceSensors[i] = state.distanceSensors[i];
			}
			//System.out.println();
			mazePanel.repaint();
		}
	}
	
	public static void main(String[] args) {
		try
		{
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());			
		}
		catch(Exception e){
		}
		MazeWalkerApp app = new MazeWalkerApp();
		app.setIconImage(GuiUtils.getImage("imgs/r2d2.png"));
		app.setSize(800, 600);
		GuiUtils.centerOnScreen(app);
		app.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		app.setVisible(true);
	}
}
